from common import load_image,load_sound,ViewPort,Tick
from gameengine import ServerGameEngine,ClientGameEngine,NoNetGameEngine
import gameobjects
from soundplayer import SoundPlayer
from player import Player
from ship import Ship

from net import DummyNetController,NetController,ServerNetController,ClientNetController
from netmessages import *
import sys
from gamemap import Base
from pygame.locals import DOUBLEBUF,FULLSCREEN,HWSURFACE,K_ESCAPE,K_SPACE,QUIT,KEYDOWN,K_y,K_n
from weapons import Shot,ShotBomb,Mine,ClusterBomb,Mine,ProxyMine,Missile
import pygame
import time
from gameobjects import Animation

import socket # only to catch giaerror

from enemies import Invader,InvaderStalker,InvaderHunter,InvaderMine,HomingMover,PathMover,GravityMover,InvaderMover


class Game:
    class Error(Exception):
        pass

    def __init__(self,map_loader,log=sys.stderr):
        self.ge = None
        self.net_controller = None
        self.settings = None
        self.delay = 2
        self.log = log
        self.map_loader = map_loader
        self.old_net_mode = None
        self.clock = pygame.time.Clock()

        self.soundplayer = SoundPlayer(pygame.mixer)
        self.info_string = self.info_string = None

    def loadData(self):
        # Set up pics to game objects
        Shot.image = load_image('data/bmp/shot.bmp', (255,0,255))
        shot_bomb = load_image('data/bmp/bomb_shot.bmp', (255,0,255))
        ShotBomb.image = shot_bomb

        image = Shot.image.convert()
        image.fill((100,100,255))
        Mine.image = image

        image = Shot.image.convert()
        image.fill((255,200,0))
        Missile.image = shot_bomb

        Ship.image = load_image('data/bmp/ship.bmp', (255,0,255))
        #Ship.image = load_image('data/bmp/Pship2.png', (255,0,255))

        #Invader.image = load_image('data/bmp/invader.bmp', (255,0,255))
        #Invader.image = load_image('data/bmp/invader2.bmp', (255,0,255))
#        anim_img = load_image('data/bmp/ship5_colorkey.bmp', (255,0,255))
#        
#        Invader.animation = Animation(anim_img,tile_width=23,tile_height=25,fps=3,spacing=1,margin=1)

    def setupSound(self,settings):
        #--------------------------------------
        # Init Sound
        channels = settings['channels']
        try:
            if     pygame.mixer:
                pygame.mixer.set_num_channels(channels)
                self.log.write( 'Sound Channels %d\n'%pygame.mixer.get_num_channels())
        except pygame.error,errmsg:
            self.log.write("%s\n"%errmsg)


        ambience_sound = None
        if settings['on']:
            # Set up Sounds
            Shot.create_sound = load_sound('data/wav/shot.wav')
            Shot.explode_sound = load_sound('data/wav/shot_explode.wav')
            ShotBomb.create_sound = load_sound('data/wav/whoosh.wav')
            ShotBomb.explode_sound = load_sound('data/wav/explode2.wav')
            ClusterBomb.create_sound = load_sound('data/wav/shot.wav')
            ClusterBomb.explode_sound = load_sound('data/wav/explode2.wav')

            Ship.explode_sound = load_sound('data/wav/explode2.wav')
            Ship.thrust_loop_sound = load_sound('data/wav/thrust_loop_mono.wav')
            Ship.thrust_start_sound = load_sound('data/wav/thrust_start_mono.wav')
            Ship.thrust_end_sound = load_sound('data/wav/thrust_end_mono.wav')

            Base.charge_sound = load_sound('data/wav/charge.wav')
            ambience_sound = load_sound('data/wav/ambience.wav')        

            Invader.explode_sound = load_sound('data/wav/explode2.wav')

            # Set volumes
            vol = settings['FxVol']
            Shot.create_sound.set_volume(vol*0.7)
            Shot.explode_sound.set_volume(vol*0.2)
            Ship.explode_sound.set_volume(vol)
            Ship.thrust_loop_sound.set_volume(vol*0.7)
            Ship.thrust_start_sound.set_volume(vol*0.7)
            Ship.thrust_end_sound.set_volume(vol*0.7)
            ShotBomb.create_sound.set_volume(vol*0.5)
            ShotBomb.explode_sound.set_volume(vol)
            Base.charge_sound.set_volume(vol*0.7)
            ambience_sound.set_volume(vol*0.7)
        return ambience_sound


    def startNetController(self,net_settings):
        pass
    def initDisplay(self,settings):
        #-------------------------------------
        # Setup display
        flags = 0
        SCREEN_W,SCREEN_H = settings['graphics']['res']
#        ' calculates the flags to give to pygame diaplay '
#        if settings['graphics']['doublebuff']:
#            settings['graphics']['flags'] |= DOUBLEBUF
#        else:
#            settings['graphics']['flags'] &= ~DOUBLEBUF
        if settings['graphics']['fullscreen']:
            settings['graphics']['flags'] |= FULLSCREEN
            flags = FULLSCREEN
        else:
            settings['graphics']['flags'] &= ~FULLSCREEN
#        if     settings['graphics']['hardware']:
#            settings['graphics']['flags'] |= HWSURFACE
#        else:
#            settings['graphics']['flags'] &= ~HWSURFACE
                
        flags = settings['graphics']['flags']
        bpp = settings['graphics']['bpp']

        # Check if display mode can be set quit if not
        if not pygame.display.mode_ok((SCREEN_W, SCREEN_H), flags,bpp):
            self.quit(1)
            raise self.Error("Cant set display mode")
        print "bpp",bpp
#        screen = pygame.display.set_mode((SCREEN_W, SCREEN_H), flags,bpp)
        screen = pygame.display.set_mode((SCREEN_W, SCREEN_H),flags)
        pygame.mouse.set_visible(0)

#===============================================================================
#        # Set pyUI screen to new screen
#        r = pyui.desktop.getRenderer()
#        r.screen = screen
#===============================================================================

        # Show display info
        if screen.get_flags() & HWSURFACE == HWSURFACE:
            print 'Hardware Accelerated'
        if screen.get_flags() & DOUBLEBUF == DOUBLEBUF:
            print 'Double Buffered'

        # load the pictures
        self.loadData()
        return screen
    
    def initMap(self,map_name):
        'creates the map object and returns it '
        self.log.write('loading Map \"%s\"\n'%map_name)
        game_map = self.map_loader.getGameMap(map_name)
        return game_map


    def initViewPorts(self,screen,no_of_players):
        h = screen.get_height()
        w = screen.get_width()
        viewports = []
        if no_of_players ==1:
            viewport = ViewPort(0,0,w,h,(0,0),screen)
            viewports.append(viewport)
        elif no_of_players ==2:
            viewport = ViewPort(0,0,w,h/2,(0,0),screen)
            viewport2 = ViewPort(0,0,w,h/2,(0,h/2),screen)
            viewports.append(viewport)
            viewports.append(viewport2)
        return viewports

    def createPlayers(self,settings,ge):
        ship_pic = Ship.image
        no_of_players = settings['game']['no_of_players']
        # Player 1
        players = []
        if no_of_players > 0:
            controls = settings['game']['players'][0]['controls']
            name = settings['game']['players'][0]['name']
            ship = Ship(ge,0,0,0,0,ship_pic)
            player = Player(ge, ship, keys=controls,name=name,home_base=254 )
            players.append(player)

        # Player 2
        if no_of_players == 2:
            controls = settings['game']['players'][1]['controls']
            name = settings['game']['players'][1]['name']
            ship = Ship(ge,0,0,0,0,ship_pic)
            player = Player(ge, ship, keys=controls,name=name )
            players.append(player)

        return players

    def startGame(self,settings):
        ' implement this in derived classes '
        pass

        
    def drawStatus(self,viewport,player,font,status_bg):
        'Draw player status. '
        info_string = "Enemies: %d Score:%d Lives:%d"%(len(self.ge.enemies),player.score,player.lives)
        if self.info_string != info_string:
            self.info_surf = (font.render(info_string, 0, (50,200,50)))
            self.info_surf.convert()
            self.info_string = info_string
        else:
            info = self.info_surf
        info = self.info_surf
        viewport.blit(info,(viewport.width-info.get_width()-10,viewport.height-info.get_height()-1))

        #Render Status bars
        status_w = viewport.width*0.1
        status_h = viewport.height*0.02
        xpos = 0
        ypos = viewport.height-status_h-2
        s = player.ship
        for v,max,c in ((s.life,s.life.max,(250,0,0)),
                        (s.fuel,s.max_fuel,(150,150,0)),
                        (s.prim_weapon.ammo,s.prim_weapon.max_ammo,(50,50,200))):
            try:
                # show warning color if value running out.
                _bg = status_bg if v.val>(v.max*0.3) else (254,0,254)
                viewport.fill(_bg,(xpos,ypos,status_w,status_h)) # draw status bar background
                viewport.fill(c,(xpos,ypos,status_w*(v.val/max),status_h)) # draw status bar 
                viewport.fill((0,0,0),(xpos+status_w*(v.max/max)-1,ypos,2,status_h)) # draw max indication
            except ZeroDivisionError:
                pass
            xpos += status_w*1.4

        # draw sec ammo
        shot_w = status_w/10
        val = s.sec_weapon.ammo.val
        for ammo in range(0,int(s.sec_weapon.ammo.max)):
            if val > ammo:
                viewport.fill(c,(xpos,ypos,shot_w,status_h))
            else:
                viewport.fill(status_bg,(xpos,ypos,shot_w,status_h))
            xpos += shot_w*2

    def stopSound(self):
        try:
            pygame.mixer.stop() 
        except:
            pass
        
    def checkQuit(self,keystate,screen,clock,dt):
        '''Check if quit event happend during play and asks Y/N'''
        if keystate[K_ESCAPE] or pygame.event.peek(QUIT):
            self.stopSound()
            self.displayMessage("Quit? y/n")            
            while 1:
                event = pygame.event.wait()
                if event.type == QUIT or (event.type == KEYDOWN and event.key == K_y):
                    #net_controller.close() # close net connection
                    return True
                elif event.type == KEYDOWN and (event.key == K_n):
                    # reset delta time before continuing
                    clock.tick()
                    dt = 0
                    self.displayMessage("Running")
                    self.net_controller.reset(0)
                    # TODO: need to also sync all clients if we ar server
                    return False

    def displayMessage(self,mess,viewport=None,size=40,color=(255,150,50)):
        'Display a message on the display'
        if viewport:
            screen = viewport
            h = screen.height
            w = screen.width
        else:
            screen = self.screen
            h = screen.get_height()
            w = screen.get_width()
        f = pygame.font.Font(None, size)
        for i,line in enumerate(mess.split('\n')):
            surf=f.render(line, 1, color)
            surf.convert()
            hpos = (h/2-surf.get_height()/2)
            hpos += (i-1)*(surf.get_height())
            screen.blit(surf,(w/2-surf.get_width()/2,hpos))
        # make changes visible 
        pygame.display.flip()

    def n_disconnect(self):
        pass

    def quit(self,stopnet=0):
        if self.ge:
            self.log.write("Shutting down GameEngine\n")
            self.ge.exit()
        if stopnet and self.net_controller:
            self.log.write("Shutting down Network\n")
            self.net_controller.close()

from game_modes import OnePlayer,DeathMatch
class NoNetGame(Game):
    def __init__(self,map_loader,log=sys.stderr):
        Game.__init__(self,map_loader,log)

    def startGame(self,settings):
        self.settings = settings
        self.net_controller = DummyNetController(self)
        nc = self.net_controller
        self.screen = pygame.display.get_surface()
        # load the pictures
        self.loadData()

        print "SCREEN",self.screen
        

        no_of_players = settings['game']['no_of_players']

        # If 2 players use Death match mode
        if no_of_players == 1:
            self.game_mode = OnePlayer(self)
        else:
            self.game_mode = DeathMatch(self)
            

        self.game_mode.runGame()

    def setupGe(self,game_map):
        '''
        Called by the game_mode object (when started by runGame)
        Reinitializes the game engine/maps etc.
        '''
        self.game_map = self.initMap(game_map)
        self.log.write("STARTING No network game\n")
        self.ge = NoNetGameEngine(self.game_map,self.net_controller,self.soundplayer)

class ServerGame(Game):
    def __init__(self,map_loader,log=sys.stderr):
        Game.__init__(self,map_loader,log)        

    def startGame(self,settings):
        self.settings = settings
        network_settings = settings['network']
        try:
            self.net_controller = self.startNetController(network_settings)
        except NetController.Error,errmsg:
            self.log.write("%s"%errmsg)
        
            self.quit()
            raise
        nc = self.net_controller
        self.screen = pygame.display.get_surface()
        # load the pictures
        self.loadData()
        self.game_map = self.initMap(settings['game']['map'])

        self.log.write("STARTING SERVER\n")
        self.ge = ServerGameEngine(self.game_map, nc,self.soundplayer)

        nc.sysTellAllPeers(NetMessGameStart(nc.id,settings['game']['map']))
        self.game_mode = DeathMatch(self)
        self.game_mode.setup(settings)
        self.game_mode.runGame()
        
        nc.tellAllPeers(NetMessGameStop(nc.id))

    def startNetController(self,net_settings):
        '''creates a the NetController and returns it'''
        # Get Network setting
        server_port = net_settings['server_port']

        # Check if we can use the current net controller
        if self.net_controller:
            print 'resetting(flushing) net controller'
            self.net_controller.reset(1)
            return self.net_controller

        print "Starting New net controller"
        # Server
        net_controller = ServerNetController(self,server_port)
        print "Server on port:%d"%server_port

        return net_controller

    def quit(self,stopnet=0):
        ' shuts down the game engine, if stopnet also stop the netcontroller'
        if self.ge:
            self.log.write("Shutting down GameEngine\n")
            self.ge.exit()
        if stopnet and self.net_controller:
            self.log.write("Shutting down Network\n")
            self.net_controller.sysTellAllPeers(NetMessDisconnect(self.net_controller.id))
            self.net_controller.close()

class ClientGame(Game):
    class ServerDisconnect(Exception):
        pass
    class ServerStartGame(Exception):
        pass
    class ServerStopGame(Exception):
        pass

    def __init__(self,map_loader,log=sys.stderr):
        Game.__init__(self,map_loader,log)        

    def startGame(self,settings):
        self.settings = settings
        network_settings = settings['network']

        try:
            self.net_controller = self.startNetController(network_settings)
        except NetController.Error,errmsg:
#            self.log.write("%s"%errmsg)
            self.quit(1)
            raise self.Error(errmsg)
            
        self.__startClientGame(settings)
        
    def __startClientGame(self,settings):
        nc = self.net_controller
        
        self.screen = pygame.display.get_surface()
        # load the pictures
        self.loadData()

        nc.sysTellAllPeers(NetMessPeerReady(nc.id))
        self.log.write("Waiting to start")

        self.displayMessage("Waiting for server to start")

        # Wait for server to tell us to start
        while 1:
            self.log.write(".")
            time.sleep(0.1)
            try:
                nc.update()
            except self.__class__.ServerStartGame:
                self.log.write("\nStarting GameEngine\n")
                break
            except self.__class__.ServerStopGame:
                pass
            except self.__class__.ServerDisconnect:
                self.log.write("\nShutting down Game\n")
                self.quit(1)
                return
            dt = self.clock.tick()
            # ping
            nc.ping(dt)

            # Check if quit
            keystate = pygame.key.get_pressed()
            if self.checkQuit(keystate,self.screen,self.clock,dt):
                self.log.write("\n")
                self.quit(1)
                return
        self.log.write("\n")

        self.game_map = self.initMap(settings['game']['map'])
        self.ge = ClientGameEngine(self.game_map,nc,self.soundplayer)
        nc.sysTellAllPeers(NetMessPeerRunningGE(nc.id))
        time.sleep(0.3)
        # run the game and catch game system events
        try:
            self._runGame()
        except self.__class__.ServerStopGame:
            if self.ge:
                self.log.write("Shutting down GameEngine\n")
                self.ge.exit()
            self.__startClientGame(settings) # recurse so we wait for server to start again
        except self.__class__.ServerStartGame:
            self.log.write("Error: Server started GameEngine when already running\n")
            return
        except self.__class__.ServerDisconnect:
            self.log.write("Shutting down Game\n")
            self.quit(1)
            return

        self.quit(1)


    def startNetController(self,net_settings):
        ''' creates the NetController
            returns netcontroller'''
        # Get Network settings
        server_addr = net_settings['server_addr']
        server_port = net_settings['server_port']
        client_port = net_settings['client_port']

        # stop the current net controller
        if self.net_controller:
            print "Stopping old net Controller"
            self.net_controller.close()
            self.net_controller = None

        # Client
        try:
            rem_ip = socket.gethostbyname(server_addr)
        except socket.gaierror:
            self.log.write("Error Could not lookup hostname:%s\n"%server_addr)
            raise
        self.log.write("Remote IP:%s Port:%s\tMy Port:%s\n"%(rem_ip,server_port,client_port))

        rem_addr = (rem_ip,server_port)
        net_controller = ClientNetController(self,client_port,rem_addr)
        try:
            net_controller.connect()
        except NetController.Error, errmsg:
            net_controller.close()
            raise
        self.log.write("Connected!\n")
        gameobjects.GameObject.ID = 4000*net_controller.id # space*id :give each player a gameobject id space

        return net_controller

    def n_startGame(self,map_name):
        ' start callback for server messages to client ' 
        self.settings['game']['map'] = map_name
        raise self.__class__.ServerStartGame("server started game\n")

    def n_stopGame(self):
        'stop callback for server messages to client '
        raise self.__class__.ServerStopGame("server stopped game\n")

    def n_disconnect(self):
        'disconnect callback for server messages to client '
        raise self.__class__.ServerDisconnect("server sent disconnect message\n")

    def quit(self,stopnet=0):
        if self.ge:
            self.log.write("Shutting down GameEngine\n")
            self.ge.exit()
        if stopnet and self.net_controller:
            self.log.write("Shutting down Network\n")
            self.net_controller.sysTellAllPeers(NetMessDisconnect(self.net_controller.id))
            self.net_controller.close()
